# FloatSpinner.py

import wx
#import wx.lib.newevent
import math


#####
# Float Spinner Control
#####
class FloatSpinner(wx.Panel):
	
	EVT_FPSC_VALUE_CHANGED_TYPE = wx.NewEventType()
	EVT_FPSC_VALUE_CHANGED = wx.PyEventBinder(EVT_FPSC_VALUE_CHANGED_TYPE, 1)
	
	
	#####
	# Constructor
	#####
	def __init__(self, parent, id = -1):
		
		'''
			Construct a new float spinner widget for wxPython
			The float spinner will initialize with default settings
			These settings can be manually configured by getters and setters
			
			@param parent: the parent for the float spinner
			@param id: the id for the float spinner
		'''
		# initial value
		self._precision = 3
		self._show_sign = True
		
		self._min = -0x8000
		self._max = 0x7fff
		self._value = 0.00
		self._max_res = math.pow(10, -self._precision)
		self._resolution = self._max_res
		
		# super constructor
		wx.Panel.__init__(self, parent, id)
		
		# add controls
		vsizer = wx.BoxSizer(wx.VERTICAL)
		
		hsizer = wx.BoxSizer(wx.HORIZONTAL)
		
		self._text = wx.TextCtrl(self, id, "0.00", style = (wx.TE_PROCESS_ENTER))
		self._spinner = wx.SpinButton(self, id, style = (wx.SP_VERTICAL))
		self._spinner.SetRange(-10, 10) # -1 to 1 would have sufficed, value gets reset every increment / decrement
		hsizer.Add(self._text, -1, wx.EXPAND)
		hsizer.Add(self._spinner, 0)
		
		vsizer.Add(hsizer, 0, wx.EXPAND)
		vsizer.AddStretchSpacer()
		
		self.SetSizer(vsizer)
		
		# bind events
		self._text.Bind(wx.EVT_TEXT_ENTER, self.Text)
		self._text.Bind(wx.EVT_SET_FOCUS, self.Select)
		self._text.Bind(wx.EVT_KILL_FOCUS, self.Text)
		self._spinner.Bind(wx.EVT_SPIN_UP, self.SpinnerUp)
		self._spinner.Bind(wx.EVT_SPIN_DOWN, self.SpinnerDown)
		
		# update control
		self.Update()
		
		
	#####
	# Getters and setters
	#####
	
	def SetValue(self, val):
		'''
			Set the value of the float spinner
			If the value is not a float, the value is ignored
			If the value exceeds the upper or lower limit, the is constrained
			
			@param val: the new value for the float spinner
		'''
		
		# assume val is floating point
		try:
			self._value = float(val)
			
		except ValueError:
			#self._text.SetValue(str(self._value))
			# RSLV: throw exception
			pass
			
		else:
			
			# limit value
			if self._value > self.GetMax():
				self._value = self.GetMax()
				# RSLV: throw exception?
			
			elif self._value < self.GetMin():
				self._value = self.GetMin()
				# RSLV: throw exception?
			
			else:
				# round value
				self._value = round(self._value, self._precision)
				
		# update control
		self.UpdateUI()
		
	
	def GetValue(self):
		'''
			Get the floating point value of the float spinner
			
			@return: floating point value
		'''
		
		return self._value
	
	
	def SetPrecision(self, val):
		'''
			Set the precision of the float spinner (number of digits)
			
			@param val: the number of digits behind the dot
		'''
		
		self._precision = val
		self._max_res = math.pow(10, -self._precision)
		self.Update()

	
	def GetPrecision(self):
		'''
			Get the precision (number of digits behind the dot)
			
			@return: the precision of the float spinner
		'''
		return self._precision
	
	
	def SetRange(self, min, max):
		'''
			Restrict the value of the float spinner to a upper and lower bound
			
			@param min: the lower bound
			@param max: the upper bound
		'''
		self._min = min
		self._max = max
		self.Update()
		
	
	def GetMin(self):
		'''
			Get the value of the lower bound
			
			@return: the lower bound
		'''
		return self._min
	
	
	def GetMax(self):
		'''
			Get the value of the upper bound
			
			@return: the upper bound
		'''
		return self._max
	
	
	def SetResolution(self, val):
		'''
			Set the resolution of the float spinner (increment step)
			
			@param val: the value of each increment step
		'''
		tmp = math.fabs(val)

		if tmp < self._max_res:
			tmp = self._max_res
			# RSLV: throw exception?
		
		self._resolution = tmp
		
		#self.Update()
	
	
	def GetResolution(self):
		'''
			Get the value of the resolution
			
			@return: the resolution
		'''
		return self._resolution
	
	
	def Increment(self):
		'''
			Increment the float spinner a single step
		'''
		self.SetValue(self.GetValue() + self.GetResolution())
		self.Update()
	
	
	def Decrement(self):
		'''
			Decrement the float spinner a single step
		'''
		self.SetValue(self.GetValue() - self.GetResolution())
		self.Update()
	
	
	# enable / disable the '+' sign in front of the value
	# the '-' sign is always enabled
	def SetShowSign(self, flag):
		'''
			Enable or disable the '+' sign in front of the value for positive values
			Negative values will always be prepended with the '-' sign
			
			@param flag: true is the '+' sign must be shown, otherwise false
		'''
		self._show_sign = flag
		self.Update()
	
	
	def GetShowSign(self):
		'''
			Get the status flag of the show sign setting
			
			@return: true is the '+' sign must be shown, otherwise false
		'''
		return self._show_sign
	
	
	def UpdateUI(self):
		'''
			Updates the UI components of the foat spinner
			
			@note: private method, should not be called by the user
		'''
		# update text and spinner
		format = "%"
		
		if self.GetShowSign():
			format = format + "+"
		
		format = format + "." + str(self.GetPrecision()) + "f"
		
		txt_val = format % self._value
		self._text.SetValue(txt_val)
		
		# reset spinner value
		self._spinner.SetValue(0)

	def Update(self):	
		'''
			Send an update message to the listeners
			
			@note: private method, should not be called by the user
		'''
		# send event
		event = wx.CommandEvent(self.EVT_FPSC_VALUE_CHANGED_TYPE, self.GetId())
		event.SetEventObject(self)
		self.GetEventHandler().ProcessEvent(event)
	
	def UpdateTotal(self):
		'''
			Updates the UI components of the foat spinner
			Send an update message to the listeners
			
			@note: private method, should not be called by the user
		'''
		self.UpdateUI()
		self.Update()
		
	#####
	# Events
	#####
	def Select(self, event):
		'''
			On facus, select all text
			
			@param event: the focus event
			@note: private method, should not be called by the user
		'''
		self._text.SetSelection(-1, -1)
		
		
	def Text(self, event):
		'''
			Text change event listener
			
			@param event: the text change event
			@note: private method, should not be called by the user
		'''
		# set new value
		self.SetValue(self._text.GetValue())
		self.UpdateTotal()
		
	def SpinnerUp(self, event):
		'''
			Increment button click event
			
			@param event: the button click event
			@note: private method, should not be called by the user
		'''
		# increment value
		self.Increment()
	
	def SpinnerDown(self, event):
		'''
			Decrement button click event
			
			@param event: the button click event
			@note: private method, should not be called by the user
		'''
		# decrement value
		self.Decrement()
		